[Amazon Cognito] New feature: Original Identity Systems Supported
This post is English translation of “[Amazon Cognito] Facebook / Google / Amazon だけじゃない!独自の認証システムも利用可能になりました!” written by Yuki Suwa, an iOS and Android Developer at underscore, Inc.
Custom Identity Provider become available
Yesterday(9/30)、Developer Authenticated Identities became available for all mobile App Developers. This feature is provided by Amazon Cognito.
- Enhanced Identity Support for Amazon Cognito | Amazon Web Services Blog
- Amazon Cognito : Announcing Developer Authenticated Identities | AWS Developer Blog - Mobile
As an Identity Provider, AWS had supported Amazon, Facebook, and Google accounts so far. However, this update enables to use any Service Provider. If you have any authentication system besides, the authenticated users can use unique Identity ID and temporary AWS credentials.
Background
So far, custom(separately developed) authentication system have been used to identify the uniqueness of the end user. Based on the system, authentication and authorization are proceeded.
In the recent years、the idea of Web identity has become general, thus the number of web services which authenticates the identity using the authentication system of Facebook, Google, Twitter and so on has increased. However, sometime you encounters problem of the Web identity system. For example, When you develop a mobile application which intends to be used for the limited number of users, you have to manage user identity by your own authentication system as Web Identity is available for all who owns the account of the public web services.
So far, such authentication cannot be handled by Amazon Cognito, but this update changed the situation.
How to use
The new feature enables to issue temporary AWS Credentials by using your own authentication system thus you need to implement a server-side authentication system. You can get Identity ID and Open ID connect token, which are issued by Cognito and temporary AWS credential can be received via STS by using these IDs.
Requirement
You need to use the latest version of AWS SDK. example version are below:
- AWS SDK for Java 1.8.11
- AWS SDK for iOS 2.0.8
- AWS SDK for Android 2.1.0
Usage
the following steps are required to get AWS Credentials using Cognito.
- Get access token of some Web Service
- Request Open ID token to your authentication system by passing the access token and Identity Pool ID as parameters.
- Get Cognito credentials based on the open ID Token acquired in the previous step.
- Now you can access AWS services by the Cognito credentials.
Implementation
I chose Java as a Server-Side language、Android as client app.
Server-Side(Java)
As a first step, let's take a look at server-side implementation. You can get Open ID Connect Token by calling GetOpenIdTokenForDeveloperIdentity API. You can get Open ID connect Token tied with the request which includes userID issued by the original authentication system. It might be good to implement with the server side login logic and return the response toward client as an additional information.
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { BasicAWSCredentials credentials = new BasicAWSCredentials("YOUR_ACCESS_KEY", "YOUR_SECRET_KEY"); AmazonCognitoIdentityClient client = new AmazonCognitoIdentityClient(credentials); // Set user ID to CustomProvider HashMap<String, String> map = new HashMap<String, String>(); map.put("CustomProvider", "userId"); // Get Open ID Connect Token GetOpenIdTokenForDeveloperIdentityRequest tokenRequest = new GetOpenIdTokenForDeveloperIdentityRequest(); // Identity Pool must be created prior to the execution. tokenRequest.setIdentityPoolId("us-east-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"); // set login inf tokenRequest.setLogins(map); // Duration of generated open ID connect token tokenRequest.setTokenDuration(1000L); GetOpenIdTokenForDeveloperIdentityResult developerIdentityResult = client.getOpenIdTokenForDeveloperIdentity(tokenRequest); // return Open ID Connect Token response.getWriter().write(developerIdentityResult.toString()); }
Client app implementation (Android)
You need to create custom provider class which extends AWSAbstractCognitoIdentityProvider abstract class. CognitoCachingCredentialsProvider used in this class handles your original provider as a identity provider for Cognito. refresh() method returns open ID connect token provided by the server program implemented formerly. getProviderName() method returns the name of your original service provider.
public class CognitoTask extends AsyncTaskLoader<Boolean> { private static final String accountId = "xxxxxxxxxxxx"; private static final String poolId = "us-east-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"; private static final String unauthArn = "arn:aws:iam::xxxxxxxxxxxx:role/Cognito_CognitoSampleUnauth_DefaultRole"; private static final String authArn = "arn:aws:iam::xxxxxxxxxxxx:role/Cognito_CognitoSampleAuth_DefaultRole"; public CognitoTask(Context context) { super(context); forceLoad(); } @Override public Boolean loadInBackground() { // generate STS instance AWSSecurityTokenServiceClient sts = new AWSSecurityTokenServiceClient(new AnonymousAWSCredentials()); sts.setRegion(Region.getRegion(Regions.US_EAST_1)); // generate CustomProvider AWSCognitoIdentityProvider provider = new CustomProvider(accountId, poolId); // Set userID HashMap<String, String> logins = new HashMap<String, String>(); map.put("CustomProvider", "userId"); provider.setLogins(logins); // receive temporary AWS credential CognitoCachingCredentialsProvider credentialsProvider = new CognitoCachingCredentialsProvider(getContext(), provider, unauthArn, authArn, sts); AWSSessionCredentials credentials = credentialsProvider.getCredentials(); Log.d("credentials" , "accessKey:" + credentials.getAWSAccessKeyId()); Log.d("credentials" , "secretKey:" + credentials.getAWSSecretKey()); return true; } /** * Implementation class of AWSAbstractCognitoIdentityProvider */ public class CustomProvider extends AWSAbstractCognitoIdentityProvider { public CustomProvider(String accountId, String identityPoolId) { super(accountId, identityPoolId); } @Override public String refresh() { return getOpenIDConnectToken(); } @Override public String getProviderName() { // return provider name (decide any proper value) return "CustomProvider"; } } /** * get Open ID connect token * @return Open ID Connect Token */ private String getOpenIDConnectToken() { String idToken = ""; return idToken; } }
You may get the temporary AWS credential by the above step.
Client app implementation (iOS)
In addition to Android, I implemented iOS. The basic process of implementation is same as Android. You need to create the subclass of AWSAbstractIdentityProvider , then implement getIdentityId: method and refresh: method. The return value is BFTask, which is a task object of Bolts. Bolts You can call any callback by using BFTaskCompletionSource.
#import <AWSCore.h> @interface CustomIdentityProvider : AWSAbstractIdentityProvider @property (nonatomic, strong) AWSCognitoIdentity *cib; @property (nonatomic, strong) NSString *accountId; @property (nonatomic, strong) NSString *identityPoolId; @property (nonatomic, strong) NSString *identityId; @property (nonatomic, strong) NSString *token; @end
#import "CustomIdentityProvider.h" @implementation CustomIdentityProvider @synthesize accountId = _accountId; @synthesize identityPoolId = _identityPoolId; @synthesize identityId = _identityId; @synthesize token = _token; - (BFTask *)getIdentityId { if (self.identityId) { // return BFTask if identityId is provided return [BFTask taskWithResult:nil]; } else { // exec refresh if identityId is not provided return [[BFTask taskWithResult:nil] continueWithBlock:^id(BFTask *task) { if (!self.identityId) { return [self refresh]; } return nil; }]; } } - (BFTask *)refresh { // get Open ID connect Token by the API request to authentication server NSURL *url = [NSURL URLWithString:@"YOUR_SERVER_API_URL"]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; BFTaskCompletionSource *source = [BFTaskCompletionSource taskCompletionSource]; [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) { if (data) { // retrieve identity ID and Open ID connect Token from the response. NSError *jsonError = nil; NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:&jsonError]; NSLog(@"result: %@", result); self.identityId = result[@"identityId"]; self.token = result[@"token"]; ; } else { NSLog(@"error: %@", error); ; } }]; return ; } @end
At last, the following code needs to be implemented. When you use iOS, set AWSServiceConfiguration to AWSServiceManager to retrieve AWS credentials.
static NSString* const kAccountId = "xxxxxxxxxxxx"; static NSString* const kIdentityPoolId = "us-east-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"; static NSString* const kUnauthArn = "arn:aws:iam::xxxxxxxxxxxx:role/Cognito_CognitoSampleUnauth_DefaultRole"; static NSString* const kAuthArn = "arn:aws:iam::xxxxxxxxxxxx:role/Cognito_CognitoSampleAuth_DefaultRole"; // generate CustomIdentityProvider CustomIdentityProvider *customIdentityProvider = [CustomIdentityProvider new]; customIdentityProvider.accountId = kAccountId; customIdentityProvider.identityPoolId = kIdentityPoolId; customIdentityProvider.logins = @{@"CustomProvider" : @"userId"}; // generate AWSCognitoCredentialsProvider AWSCognitoCredentialsProvider *provider = [[AWSCognitoCredentialsProvider alloc] initWithRegionType:AWSRegionUSEast1 identityProvider:customIdentityProvider unauthRoleArn:kUnAuthArn authRoleArn:kAuthArn]; // register AWS credential AWSServiceConfiguration *configuration = [AWSServiceConfiguration configurationWithRegion:AWSRegionUSEast1 credentialsProvider:provider]; [AWSServiceManager defaultServiceManager].defaultServiceConfiguration = configuration; // Authentication [[provider getIdentityId] continueWithSuccessBlock:^id(BFTask *task){ NSString* cognitoId = provider.identityId; NSLog(@"cognitoId: %@", cognitoId); NSLog(@"logins: %@", provider.logins); return nil; }];
Summary
At the moment of the release of Amazon Cognito, some developer said "Why Twitter isn't supported?". But I think this new feature of Cognito can spread the possibility of the mobile app further.